package com.idea.relax.log.trace;

import com.idea.relax.log.support.utils.StringUtil;

import java.util.UUID;

/**
 * @className: GlobalIdGenerator
 * @description: 参考skywalking的源码, 该类的作用：生成分布式TraceId
 * @author: salad
 * @date: 2023/1/9
 **/
public final class GlobalIdGenerator {

    /**
     * 线程序列号不能超过该值，最大为：MAX_THREAD_SEQ - 1
     */
    private static final short MAX_THREAD_SEQ = 10000;

    /**
     * 进程ID
     */
    private static final String PROCESS_ID = UUID.randomUUID().toString().replaceAll("-", "");

    private static final ThreadLocal<IDContext> THREAD_ID_SEQUENCE = ThreadLocal.withInitial(
            () -> new IDContext(System.currentTimeMillis(), (short) 0));

    private GlobalIdGenerator() {
    }

    /**
     * TraceId的构成：
     * 进程ID.线程号.当前时间戳x10000 + 当前进程中的一个序列（范围是0-9999）
     */
    public static String generate() {
        return StringUtil.join(
                ".",
                PROCESS_ID,
                String.valueOf(Thread.currentThread().getId()),
                String.valueOf(THREAD_ID_SEQUENCE.get().nextSeq())
        );
    }

    private static class IDContext {
        //上次生成seq序列号的时间戳
        private long lastTimestamp;
        //线程的序列号
        private short threadSeq;
        // Just for considering time-shift-back only.
        //上次时钟回拨的时间戳，时间正常是一直向前的，但是有些情况，比如人为回退了计算机时间、计算机故障等等，就会导致时间倒退，这就是时钟回拨
        private long lastShiftTimestamp;
        //发生一次时钟回拨，这个数就+1
        private int lastShiftValue;

        private IDContext(long lastTimestamp, short threadSeq) {
            this.lastTimestamp = lastTimestamp;
            this.threadSeq = threadSeq;
        }

        private long nextSeq() {
            return timestamp() * 10000 + nextThreadSeq();
        }

        private long timestamp() {
            long currentTimeMillis = System.currentTimeMillis();
            //如果当前时间小于上次序列号生产时间，说明发生了时钟回拨
            if (currentTimeMillis < lastTimestamp) {
                // Just for considering time-shift-back by Ops or OS. @hanahmily 's suggestion.
                if (lastShiftTimestamp != currentTimeMillis) {
                    lastShiftValue++;
                    lastShiftTimestamp = currentTimeMillis;
                }
                return lastShiftValue;
            } else {
                lastTimestamp = currentTimeMillis;
                return lastTimestamp;
            }
        }

        private short nextThreadSeq() {
            if (threadSeq == MAX_THREAD_SEQ) {
                threadSeq = 0;
            }
            return threadSeq++;
        }
    }
}
